[Swift 공식문서 읽기]Memory Safety
안녕하세요. 엘림입니다🙇🏻♀️
Swift 공식 문서를 정독하기 시리즈입니다!
제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!
좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ
자, 그럼 시작해볼까요
이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏
메모리 안정성
기본적으로 Swift는 코드가 비정상적으로 동작하는 것을 막는 행위를 합니다. 예를 들어 변수가 초기화되기 전에 사용된다거나, 메모리에서 헤제된 값에 접근한다거나, 인덱스의 한계를 넘겨서 사용하려고 하는 것 등이 있습니다. 또한 Swift에서는 메모리의 위치를 수정하는 코드가 해당 메모리에 독점적으로 액세스할 수 있도록 요구함으로써, 동일한 메모리 영역에 대한 다중 액세스가 충돌하지 않도록 합니다.
이렇듯 메모리 관리를 자동으로 관리해주기 때문에 대부분의 경우에는 메모리 접근에 대해 생각하지 않아도 됩니다. 하지만 메모리 접근 충돌이 발생할 수 있는 잠재적인 상황을 이해하여, 메모리 접근 충돌을 피하는 코드를 어떻게 작성할 수 있는지 이해하는 것은 중요합니다.
(만약 메모리 접근 충돌이 일어나면 런타임 혹은 컴파일 에러가 발생합니다.)
메모리 접근 충돌의 이해
코드에서 메모리 접근은 변수에 값을 할당하거나, 접근할 때 발생합니다.
// A write access to the memory where one is stored.
var one = 1
// A read access from the memory where one is stored.
print("We're number \(one)!")
메모리에 대한 액세스 충돌은 코드의 다른 부분이 동시에 메모리의 동일한 위치에 액세스하려고 할 때 발생할 수 있습니다. 메모리의 한 위치에 동시에 여러번 접근하면, 예측할 수 없거나 일관성이 없는 동작이 발생할 수 있습니다. 즉, 메모리 충돌은 값을 할당하고, 접근하는 것을 동시에 수행할때 발생합니다.
(만약 동시성(cuncurrent) 코드나, 멀티쓰레드 코드를 작성한적이 있다면 이 메모리 접근 충돌 문제는 익숙한 문제일 것 입니다. 하지만 이 접근 충돌 문제는 싱글 쓰레드에서 발생할 수 있는 문제이고 동시성과 멀티쓰레드와 관련이 없습니다.)
메모리 접근의 특성
구체적인 메모리 충돌이 발생할 수 있는 상황은 다음 3가지 조건 중 2가지를 만족하면 발생합니다.
- 최소 하나의 쓰기 접근 상황
- 메모리의 같은 위치를 접근할 때
- 접근 지속시간이 겹칠때
쓰기 접근은 메모리의 위치를 변경하고, 읽기는 그렇지 않습니다.
메모리의 위치는 무엇을 참조하고 있는지 나타냅니다.
메모리 접근의 지속시간은 즉각적인 접근과 장기 접근으로 구분할 수 있습니다.
즉각적인 접근은 코드에서 메모리 접근이 시작되고 끝나기 전에, 그 메모리에 대한 접근이 시작될 수 없음을 의미합니다.
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// Prints "2"
위와 같은 경우가 즉각적인 접근이며, 이러한 경우 메모리 접근의 충돌이 발생하지 않습니다.
(대부분의 메모리 액세스는 즉각적입니다.)
반대로 장기 액세스는, 장기 액세스가 시작된 후 종료되기 전에 다른 코드가 실행될 수 있으며 이를 오버랩이라고 합니다.
in-out 파라미터의 충돌 접근
함수는 모든 in-out 매개변수에 대한 장기 쓰기 접근 권한을 가집니다.
in-out 매개변수에 대한 쓰기 액세스는 모든 non-in-out 매개변수가 평가된 후 시작되고 해당 함수 호출의 전체 기간 동안 지속됩니다. 인-아웃 매개변수가 여러 개인 경우 쓰기 액세스는 매개변수가 나타나는 순서대로 시작됩니다.
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
increment의 파라미터로 inout Int의 number를 사용합니다. 그리고 함수 내부에서 인자로 사용한 number를 변경합니다. 이 경우 인자로 number를 넣고, 또 number를 읽어 그 number에 stepSize를 추가해 다시 할당하는 쓰기와 읽기가 동시에 발생해 접근 충돌이 일어 납니다.
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(©OfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2
이 문제를 해결하기 위한 한가지 방법은 stepSize의 복사본을 명시적으로 사용하는 것입니다. stepSize를 복사한 copyOfStepSize를 사용하면 하나의 메모리를 읽고 쓰는 행위를 동시에 하지 않게돼 접근 충돌을 피할 수 있습니다.
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore)
// Error: conflicting accesses to playerOneScore
위와 같이 동일한 함수에 여러 in-out 매개변수에 대한 인수로, 같은 단일 변수를 전달하면 충돌이 발생합니다.
(연산자도 함수이기 때문에 in-out 파라미터 장기 접근으로 충돌이 발생합니다.)
메소드에서 self를 사용했을때의 충돌
구조체의 mutating 메서드의 self는 호출 기간 동안 쓰기 권한을 가집니다.
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // OK
이 경우는 oscar와 maria둘다 다른 구조체 인스턴스 이기 때문에 체력을 공유해도 아래와 같이 아무 문제가 없습니다.
하지만 만약 oscar를 자신과 같은 인스턴스인 oscar와 체력을 공유한다고 실행하면 어떻게 될까요?
oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar
체력을 읽어오고 읽어온 체력을 변경하는 동작을 한 메모리 위치에서 동시에 수행하게 돼서 충돌이 발생합니다.
프로퍼티 접근 충돌
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // OK
}
구조체, 튜플 및 열거와 같은 유형은 값 유형이기 때문에, 값 일부를 변경하면 전체 값이 변경됩니다. 그렇기 때문에 튜플의 요소를 지금처럼 같은 함수에 둘 다 in-out으로 호출하게되면, 중복 쓰기에 대한 접근이 발생하므로 충돌이 일어납니다.
(??? 공식문서 한번 더 확인해보기..!)
구조체에서 프로퍼티를 접근하는데, 오버래핑 접근으로부터 안전한 상황은 다음과 같습니다.
- 구조체 인스턴스에서 저장프로퍼티만 접근하고, 계산된 프로퍼티 혹은 클래스 프로퍼티를 접근하지 않을 때
- 구조체가 전역변수가 아니라 지역변수 일때
- 구조체가 어떤 클로저로부터 캡쳐하지 않거나, nonescaping 클로저에서만 획득된 경우
만약 컴파일러가 접근이 안전하지 않다고 판단하면, 접근 자체가 안됩니다.
오늘도 스위프트 공식문서를 정리해보았군욥~
다음편도 힘내보겠습니다!
감사합니다🙇🏻♀️
Author And Source
이 문제에 관하여([Swift 공식문서 읽기]Memory Safety), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@lina0322/Swift공식문서-읽기Memory-Safety저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)